﻿using Autodesk.Revit.DB;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CoreDB.Extensions
{
    /// <summary>
    /// Boundingbox 与 Outline 本来就是同一个东西，所以这里写在一起
    /// 所以，写所有方法都要同时兼顾两者
    /// </summary>
    public static class BoundingboxExtension
    {
        private const double defaultEps = 1e-3;

        #region Init
        /// <summary>
        /// Init后不可直接使用
        /// </summary>
        /// <param name="box"></param>
        public static void Init(this BoundingBoxXYZ box)
        {
            box.Min = new XYZ(double.MaxValue, double.MaxValue, double.MaxValue);
            box.Max = new XYZ(double.MinValue, double.MinValue, double.MinValue);
        }
        public static void Init(this Outline ot)
        {
            ot.MinimumPoint = new XYZ(double.MaxValue, double.MaxValue, double.MaxValue);
            ot.MaximumPoint = new XYZ(double.MinValue, double.MinValue, double.MinValue);
        }
        #endregion

        #region Build
        /// <summary>
        /// 创建 Curve 的 BoundingBox
        /// </summary>
        /// <param name="curve"></param>
        /// <returns></returns>
        public static BoundingBoxXYZ BuildBoundingBox(this Curve curve)
        {
            // 初始化 BoundingBox
            BoundingBoxXYZ box = new BoundingBoxXYZ();
            box.Init();
            foreach (var pt in curve.Vertices())
                box.AddPoint(pt);
            return box;
        }
        public static Outline BuildOutline(this Curve curve)
        {
            Outline ot = new Outline(XYZ.Zero, XYZ.Zero);
            ot.Init();
            foreach (var pt in curve.Vertices())
                ot.AddPoint(pt);
            return ot;
        }
        /// <summary>
        /// 通过给定点构造Outline
        /// </summary>
        /// <param name="point"></param>
        /// <param name="radius">扩大范围，即正方体边长的一半</param>
        /// <param name="zvalue">对Z进行特殊指定，如果不指定则使用radius</param>
        /// <returns></returns>
        public static Outline BuildOutLine(this XYZ point, double radius = 0.3, double zvalue = double.NaN)
        {
            zvalue = double.IsNaN(zvalue) ? radius : zvalue;
            XYZ min = new XYZ(point.X - radius, point.Y - radius, point.Z - zvalue);
            XYZ max = new XYZ(point.X + radius, point.Y + radius, point.Z + zvalue);
            Outline outline = new Outline(min, max);
            return outline;
        }
        /// <summary>
        /// 通过给定点构造BoundingBox
        /// </summary>
        /// <param name="point"></param>
        /// <param name="radius"></param>
        /// <param name="zvalue"></param>
        /// <returns></returns>
        public static BoundingBoxXYZ BuildBoundingBox(this XYZ point, double radius = 0.3, double zvalue = double.NaN)
        {
            zvalue = double.IsNaN(zvalue) ? radius : zvalue;
            BoundingBoxXYZ box = new BoundingBoxXYZ();
            box.Min = new XYZ(point.X - radius, point.Y - radius, point.Z - zvalue);
            box.Max = new XYZ(point.X + radius, point.Y + radius, point.Z + zvalue);
            return box;
        }
        #endregion

        #region AddPoint
        /// <summary>
        /// 通过指定点修改包围盒的大小
        /// Outline自身已经具备此方法(AddPoint)，所以这里忽略
        /// </summary>
        /// <param name="bBox"></param>
        /// <param name="pt"></param>
        public static void AddPoint(this BoundingBoxXYZ bBox, XYZ pt)
        {
            if (bBox.Enabled)
            {
                bBox.Max = new XYZ(Math.Max(bBox.Max.X, pt.X), Math.Max(bBox.Max.Y, pt.Y), Math.Max(bBox.Max.Z, pt.Z));
                bBox.Min = new XYZ(Math.Min(bBox.Min.X, pt.X), Math.Min(bBox.Min.Y, pt.Y), Math.Min(bBox.Min.Z, pt.Z));
            }
            else
            {
                bBox.Max = pt;
                bBox.Min = pt;
                bBox.Enabled = true;
            }
        }
        #endregion

        #region IsInside
        /// <summary>
        /// 判断点是否在 Outline 范围内
        /// </summary>
        /// <param name="point"></param>
        /// <param name="ot"></param>
        /// <param name="ignoreZ">True:忽略Z轴</param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsInBox(this XYZ point, Outline ot, bool ignoreZ = false, double eps = defaultEps)
        {
            bool isInsideXY = point.X.IsMoreOrEqual(ot.MinimumPoint.X, eps) && point.X.IsLessOrEqual(ot.MaximumPoint.X, eps)
                        && point.Y.IsMoreOrEqual(ot.MinimumPoint.Y, eps) && point.Y.IsLessOrEqual(ot.MaximumPoint.Y, eps);
            if (!isInsideXY)
                return false;
            return ignoreZ || (point.Z.IsMoreOrEqual(ot.MinimumPoint.Z, eps) && point.Z.IsLessOrEqual(ot.MaximumPoint.Z, eps));
        }
        /// <summary>
        /// 判断点是否在 BoundingBox 范围内
        /// </summary>
        /// <param name="point"></param>
        /// <param name="bBox"></param>
        /// <param name="ignoreZ">True：忽略Z轴</param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsInBox(this XYZ point, BoundingBoxXYZ bBox, bool ignoreZ = false, double eps = defaultEps)
        {
            bool isInsideXY = point.X.IsMoreOrEqual(bBox.Min.X, eps) && point.X.IsLessOrEqual(bBox.Max.X, eps)
                        && point.Y.IsMoreOrEqual(bBox.Min.Y, eps) && point.Y.IsLessOrEqual(bBox.Max.Y, eps);
            if (!isInsideXY)
                return false;
            return ignoreZ || (point.Z.IsMoreOrEqual(bBox.Min.Z, eps) && point.Z.IsLessOrEqual(bBox.Max.Z, eps));
        }
        #endregion

        #region 获取某个面的点或线 GetVertices Lines 
        /// <summary>
        /// 获取Outline任意面的四个点
        /*
         *                            p8                            p7  
                                       +----------------------------+ (max) 
                                     ,`                           ,'|  
                                   ,'  |                         /  |  
                                  -            top             ,`   |  
                                ,'     |                      /     |  
                            p5+`----------------------------+`p6    |  
                              |        |                    |       |  
                              |                             |       |  
                              |        |                    |       |  
                              |                 back        |       |  
                              |        |                    |       |  
                              | left                        | right |  
                              |        |                    |       |  
                              |          front              |       |  
                              |        |                    |       |  
                              |        +- - - - - - - - - - - - - - +p3
                              |      , p4                   |      ,`  
                z             |    ,                        |    ,'    
                |  ,y         |              Bottom         |   -      
                | /           |  '                          | ,'       
                +`----x  (min)+`----------------------------+`p2         
                              p1                             
            */
        /// </summary>
        /// <param name="outline"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public static List<XYZ> GetVertices(this Outline outline, FaceDirection direction = FaceDirection.Bottom)
        {
            XYZ minPt = outline.MinimumPoint;
            XYZ maxPt = outline.MaximumPoint;
            var twoPts = new List<XYZ>() { minPt, maxPt };
            switch (direction)
            {
                case FaceDirection.Top:
                    minPt = minPt.ProjectM(maxPt, XYZ.BasisZ).projPt; break;
                case FaceDirection.Bottom:
                    maxPt = maxPt.ProjectM(minPt, XYZ.BasisZ).projPt; break;
                case FaceDirection.Left:
                    maxPt = maxPt.ProjectM(minPt, XYZ.BasisX).projPt; break;
                case FaceDirection.Right:
                    minPt = minPt.ProjectM(maxPt, XYZ.BasisX).projPt; break;
                case FaceDirection.Front:
                    maxPt = maxPt.ProjectM(minPt, XYZ.BasisY).projPt; break;
                case FaceDirection.Back:
                    minPt = minPt.ProjectM(maxPt, XYZ.BasisY).projPt; break;
                case FaceDirection.All:
                    break;
            }
            return twoPts.FindMatrixPts();
        }
        /// <summary>
        /// 同上
        /// </summary>
        /// <param name="bBox"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public static List<XYZ> GetVertices(this BoundingBoxXYZ bBox, FaceDirection direction = FaceDirection.Bottom)
        {
            return new Outline(bBox.Min, bBox.Max).GetVertices(direction);
        }
        /// <summary>
        /// 获取Outline的特定的面的边（不保证首尾相连）
        /// </summary>
        /// <param name="outline"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public static List<Line> GetEdges(this Outline outline, FaceDirection direction = FaceDirection.Bottom)
        {
            var vectors = outline.GetVertices(direction);
            return vectors.FindMatrixLines();
        }
        /// <summary>
        /// 获取BoundingBox的特定的面的边（不保证首尾相连）
        /// </summary>
        /// <param name="outline"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public static List<Line> GetEdges(this BoundingBoxXYZ bBox, FaceDirection direction = FaceDirection.Bottom)
        {
            var vectors = bBox.GetVertices(direction);
            return vectors.FindMatrixLines();
        }
        #endregion

        #region Outline 与 BoundingBoxXYZ 的互转
        public static Outline GetOutline(this BoundingBoxXYZ bBox)
        {
            return new Outline(bBox.Min, bBox.Max);
        }
        public static BoundingBoxXYZ GetBoundingBoxXYZ(this Outline outline)
        {
            return new BoundingBoxXYZ() { Min = outline.MinimumPoint, Max = outline.MaximumPoint };
        }
        #endregion

        #region 求中心点
        public static XYZ Center(this BoundingBoxXYZ box)
        {
            return (box.Min + box.Max) / 2;
        }
        public static XYZ Center(this Outline ot)
        {
            return (ot.MinimumPoint + ot.MaximumPoint) / 2;
        }
        #endregion

        #region Offset
        /// <summary>
        /// OutLine 的偏移
        /// </summary>
        /// <param name="ot"></param>
        /// <param name="dir"></param>
        /// <param name="dis"></param>
        /// <returns></returns>
        public static Outline Offset(this Outline ot, XYZ dir, double dis)
        {
            return new Outline(ot.MinimumPoint.Offset(dir, dis), ot.MaximumPoint.Offset(dir, dis));
        }
        /// <summary>
        /// BoundingBox 的偏移
        /// </summary>
        /// <param name="bBox"></param>
        /// <param name="dir"></param>
        /// <param name="dis"></param>
        /// <returns></returns>
        public static BoundingBoxXYZ Offset(this BoundingBoxXYZ bBox, XYZ dir, double dis)
        {
            return new BoundingBoxXYZ() { Enabled = true, Min = bBox.Min.Offset(dir, dis), Max = bBox.Max.Offset(dir, dis) };
        }
        #endregion

        #region 包围盒求交集
        public static BoundingBoxXYZ BoundingBoxIntersect(this BoundingBoxXYZ bb1, BoundingBoxXYZ bb2, double eps = defaultEps)
        {
            Outline ot = bb1.GetOutline().OutlineIntersect(bb2.GetOutline(), eps);
            if (ot != null)
                return ot.GetBoundingBoxXYZ();
            return null;
        }
        public static Outline OutlineIntersect(this Outline ot1, Outline ot2, double eps = defaultEps)
        {
            if (!ot1.Intersects(ot2, eps))
                return null;

            double xMax = Math.Min(ot1.MaximumPoint.X, ot2.MaximumPoint.X);
            double xMin = Math.Max(ot1.MinimumPoint.X, ot2.MinimumPoint.X);
            double yMax = Math.Min(ot1.MaximumPoint.Y, ot2.MaximumPoint.Y);
            double yMin = Math.Max(ot1.MinimumPoint.Y, ot2.MinimumPoint.Y);
            double zMax = Math.Min(ot1.MaximumPoint.Z, ot2.MaximumPoint.Z);
            double zMin = Math.Max(ot1.MinimumPoint.Z, ot2.MinimumPoint.Z);
            if (xMax.IsMoreOrEqual(xMin) && yMax.IsMoreOrEqual(yMin) && zMax.IsMoreOrEqual(zMin))
                return new Outline(new XYZ(xMin, yMin, zMin), new XYZ(xMax, yMax, zMax));
            else
                return null;
        }
        #endregion

        #region Transform变换 OfTrans
        public static Outline OfTrans(this Outline outline, Transform transform, bool inverse = false)
        {
            if (outline is null)
                throw new ArgumentNullException(nameof(outline));

            if (transform is null)
                throw new ArgumentNullException(nameof(transform));

            if (!transform.BasisX.IsParallel(XYZ.BasisX, defaultEps) || !transform.BasisX.IsParallel(XYZ.BasisY, defaultEps))
                throw new InvalidDataException("Please use solid intersection filter to filter elements.");
            var min = outline.MinimumPoint.OfTransPt(transform, inverse);
            var max = outline.MaximumPoint.OfTransPt(transform, inverse);

            var pts = new List<XYZ>() { min, max };
            var xs = pts.FindMatrixDoubles(0);
            var ys = pts.FindMatrixDoubles(1);
            var zs = pts.FindMatrixDoubles(2);
            if (xs.Count > 0 && ys.Count > 0 && zs.Count > 0)
                return new Outline(new XYZ(xs.Min(), ys.Min(), zs.Min()), new XYZ(xs.Max(), ys.Max(), ys.Max()));

            return null;
        }
        public static BoundingBoxXYZ OfTrans(this BoundingBoxXYZ bBox, Transform transform, bool inverse = false)
        {
            var ot = bBox.GetOutline().OfTrans(transform, inverse);
            if (ot != null)
                return ot.GetBoundingBoxXYZ();
            return null;
        }
        #endregion



    }
    /// <summary>
    /// 用于表达 BoundingBox 的 Face 朝向枚举
    /// </summary>
    public enum FaceDirection
    {
        /// <summary>
        /// Top direction.
        /// </summary>
        Top,
        /// <summary>
        /// Bottom direction.
        /// </summary>
        Bottom,
        /// <summary>
        /// Left direction.
        /// </summary>
        Left,
        /// <summary>
        /// Right direction.
        /// </summary>
        Right,
        /// <summary>
        /// Front direction.
        /// </summary>
        Front,
        /// <summary>
        /// Back direction.
        /// </summary>
        Back,
        /// <summary>
        /// 所有的边或点
        /// </summary>
        All
    }
}
